﻿using SharpDX.DirectSound;
using SharpDX.Multimedia;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace PeerComLink
{
    public class ComChannel : IDisposable
    {
        UdpClient _udpClient;
        UdpClient _udpClientRecv;
        IPEndPoint _sendEP;
        BinaryFormatter _binFormatter;
        AudioPlaybackController _playbackController;
        int _recvPort, _sendPort;

        public ComChannel()
        {

            _binFormatter = new BinaryFormatter();
            _udpClient = new UdpClient();

            if (App.Mode == 1)
            {
                _recvPort = 1337;
                _sendPort = 1338;
            }
            else if (App.Mode == 2)
            {
                _recvPort = 1338;
                _sendPort = 1337;
            }
            else
            {
                _recvPort = 1337;
                _sendPort = 1337;
            }

            _sendEP = new IPEndPoint(IPAddress.Broadcast, _sendPort);
            try
            {
                _udpClientRecv = new UdpClient(_recvPort);
                _udpClientRecv.BeginReceive(new AsyncCallback(Receive), null);
            }
            catch (Exception) { }

            _playbackController = new AudioPlaybackController();
        }

        public void Receive(IAsyncResult res)
        {
            lock (_udpClientRecv)
            {
                _udpClientRecv.BeginReceive(new AsyncCallback(Receive), null);
                IPEndPoint ep = new IPEndPoint(IPAddress.Any, _recvPort);
                byte[] receiveBytes = _udpClientRecv.EndReceive(res, ref ep);
                MemoryStream memoryStream = new MemoryStream(receiveBytes);
                switch ((MessageType)memoryStream.ReadByte())
                {
                    case MessageType.Introduction:
                        ReceiveIntroduction(memoryStream, ep.Address.ToString());
                        break;
                    case MessageType.IntroductionReply:
                        ReceiveIntroductionReply(memoryStream, ep.Address.ToString());
                        break;
                    case MessageType.CallControl:
                        ReceiveCallControl(memoryStream, ep.Address.ToString());
                        break;
                    case MessageType.CallData:
                        ReceiveCallData(memoryStream, ep.Address.ToString());
                        break;
                }
            }
        }

        private void ReceiveCallData(MemoryStream memoryStream, string remoteHost)
        {
            lock (_playbackController)
            {
                byte[] data = new byte[memoryStream.Length - memoryStream.Position];
                memoryStream.Read(data, 0, data.Length);
                _playbackController.WriteData(data);
            }
        }

        private void ReceiveIntroduction(MemoryStream memoryStream, string remoteHost)
        {
            this.SendIntroductionReply(remoteHost);
            ReceiveIntroductionReply(memoryStream, remoteHost);
        }

        private void ReceiveIntroductionReply(MemoryStream memoryStream, string remoteHost)
        {
            Member member = (Member)_binFormatter.Deserialize(memoryStream);
            if (member.ID != App.RunningData.LocalMember.ID)
            {
                member.RemoteIP = remoteHost;
                var existingMember = App.RunningData.MemberStatuses.FirstOrDefault(x => x.Recepient.ID == member.ID);
                if (existingMember != null)
                {
                    App.RunningData.MemberStatuses.Remove(existingMember);
                }
                App.RunningData.MemberStatuses.Add(new MemberStatus
                {
                    Recepient = member,
                    State = MemberStatus.MemberState.Standby
                });
            }
        }

        public void ReceiveCallControl(MemoryStream memoryStream, string remoteHost)
        {
            var memberStatus = App.RunningData.MemberStatuses.FirstOrDefault(x => x.Recepient.RemoteIP == remoteHost);
            if (memberStatus != null)
            {
                switch ((CallControlSignal)memoryStream.ReadByte())
                {
                    case CallControlSignal.Initiate:
                        //MessageBox.Show("Receiving call..");
                        memberStatus.State = MemberStatus.MemberState.RequestReceived;
                        break;
                    case CallControlSignal.Accept:
                        //MessageBox.Show("Call accepted");
                        memberStatus.State = MemberStatus.MemberState.Connected;
                        Task.Factory.StartNew(() => StartTransmissionLoop(memberStatus));
                        break;
                    case CallControlSignal.Reject:
                        //MessageBox.Show("Call rejected");
                        memberStatus.State = MemberStatus.MemberState.Standby;
                        break;
                    case CallControlSignal.End:// end transmission
                        this.SendCallControlSignal(memberStatus, CallControlSignal.End);
                        memberStatus.State = MemberStatus.MemberState.Standby;
                        break;
                }
            }
        }

        void IDisposable.Dispose()
        {
            _udpClient.Close();
            _udpClientRecv.Close();
        }

        enum MessageType : byte
        {
            Introduction = 0x01,
            IntroductionReply = 0x02,
            CallControl = 0x03,
            CallData = 0x04
        }

        enum CallControlSignal : byte
        {
            Initiate = 0x01,
            Accept = 0x02,
            Reject = 0x03,
            End = 0x04
        }

        public void InitiateCall(MemberStatus memberStatus)
        {
            this.SendCallControlSignal(memberStatus, ComChannel.CallControlSignal.Initiate);
            memberStatus.State = MemberStatus.MemberState.RequestSent;
        }

        public void AcceptCall(MemberStatus memberStatus)
        {
            this.SendCallControlSignal(memberStatus, CallControlSignal.Accept);
            memberStatus.State = MemberStatus.MemberState.Connected;
            Task.Factory.StartNew(() => StartTransmissionLoop(memberStatus));
        }

        public void EndCall(MemberStatus memberStatus)
        {
            this.SendCallControlSignal(memberStatus, ComChannel.CallControlSignal.End);
            memberStatus.State = MemberStatus.MemberState.Standby;
        }

        private void SendCallControlSignal(MemberStatus memberStatus, CallControlSignal signal)
        {
            MemoryStream ms = new MemoryStream();
            ms.WriteByte((byte)MessageType.CallControl);
            ms.WriteByte((byte)signal);

            byte[] buffer = ms.ToArray();
            _udpClient.Send(buffer, buffer.Length, memberStatus.Recepient.RemoteIP, _sendPort);
        }

        public void SendIntroductionBroadcast(Member localMember)
        {
            MemoryStream ms = new MemoryStream();
            ms.WriteByte((byte)MessageType.Introduction);
            _binFormatter.Serialize(ms, localMember);

            byte[] sendBytes = ms.ToArray();

            _udpClient.Send(sendBytes, sendBytes.Length, _sendEP);
        }

        public void SendIntroductionReply(string remoteHost)
        {
            MemoryStream ms = new MemoryStream();
            ms.WriteByte((byte)MessageType.IntroductionReply);
            _binFormatter.Serialize(ms, App.RunningData.LocalMember);

            byte[] sendBytes = ms.ToArray();

            _udpClient.Send(sendBytes, sendBytes.Length, remoteHost, _sendPort);
        }

        public void StartTransmissionLoop(MemberStatus memberStatus)
        {
            try
            {
                AudioCaptureController ac = new AudioCaptureController();
                while (memberStatus.State == MemberStatus.MemberState.Connected)
                {
                    MemoryStream ms = new MemoryStream();
                    ms.WriteByte((byte)MessageType.CallData);
                    byte[] audioBytes = ac.ReadData();
                    ms.Write(audioBytes, 0, audioBytes.Length);
                    byte[] bytes = ms.ToArray();
                    _udpClient.Send(bytes, bytes.Length, memberStatus.Recepient.RemoteIP, _sendPort);
                }
            }            
            catch(Exception)
            {
            }
        }

    }
}
